package ru.papergames.battleserver.model;

import javolution.util.FastMap;
import org.apache.log4j.Logger;

/**
 * @author: ATroshanin
 * Date: 15.06.2010
 * Time: 14:45:56
 */
public class UsersManager {

    private static final Logger logger = Logger.getLogger(UsersManager.class.getName());
    private static final long PING_TIMEOUT = 2*60*1000;
    private static final long WAIT_FOR_USERS_CONNECT_TIMEOUT = 60*1000;

    private static UsersManager instance = new UsersManager();

    private FastMap<Long, ConnectingFlashUser> newFlashUsers = new FastMap<Long, ConnectingFlashUser>().shared();

    private FastMap<Long, AbstractPaperGameUser> allUsers = new FastMap<Long, AbstractPaperGameUser>().shared();

    private ThreadController threadController;

    private UsersManager() {
        threadController = new ThreadController(this);
    }

    public static UsersManager getInstance() {
        return instance;
    }

    public ConnectingFlashUser popFlashUserFromNewUsers(long playerId) {
        return newFlashUsers.remove(playerId);
    }

    public void pushFlashUserInNewUsers(ConnectingFlashUser usr) {
        newFlashUsers.put(usr.getPlayer().getId(), usr);
    }

    public AbstractPaperGameUser getPaperGameUser(long playerId) {
        return allUsers.get(playerId);
    }

    public FastMap<Long, AbstractPaperGameUser> getAllPaperGameUsers() {
        return allUsers;
    }

    public void addConnectedUserToRoom(AbstractPaperGameUser user) {
        allUsers.put(user.getId(), user);
    }

    public void onUserDisconnection(AbstractPaperGameUser user) {
        if (user == null) {
            return;
        }
        allUsers.remove(user.getId());
    }

    public void clearAllUsersAndRooms() {
        allUsers.clear();
    }

    public void start() {
        threadController.start();
    }

    public void shutDown() {
        threadController.shutDown();
    }

    private class ThreadController extends Thread{

        private UsersManager usersManager;
        private volatile boolean shutdown;

        private ThreadController(UsersManager usersManager) {
            this.usersManager = usersManager;
        }

        public void run () {
            int pingIndx = 0;
            int checkNewUsersIndx = 0;
            while (!shutdown) {
                try {
                    checkFlashUsersDetached();
                } catch (Exception e) {
                    logger.warn(e);
                }

                try {
                    pingIndx++;
                    if (pingIndx >= 6) {
                        pingIndx = 0;
                        checkFlashUsersPing();
                    }
                } catch (Exception e) {
                    logger.warn(e);
                }

                try {
                    checkNewUsersIndx++;
                    if (checkNewUsersIndx >= 6) {
                        checkNewUsersIndx = 0;
                        checkNewFlashUsers();
                    }
                } catch (Exception e) {
                    logger.warn(e);
                }

                try {
                    if (!shutdown) {
                        Thread.sleep(10*1000);
                    }
                } catch (InterruptedException e) {
                    //
                    logger.info("BattleManager$ThreadController was interrupted while sleep");
                }
            }
        }

        private void checkNewFlashUsers() {
            logger.debug("checkNewFlashUsers {newFlashUsers size: " + newFlashUsers.size() + "} if timeout occured");
            long curTime = System.currentTimeMillis();
            for (FastMap.Entry<Long, ConnectingFlashUser> e = newFlashUsers.head(), end = newFlashUsers.tail(); (e = e.getNext()) != end;) {
                ConnectingFlashUser connectingFlashUser = e.getValue();
                long waitingTime = curTime - connectingFlashUser.getStartWaitingForUsersConnectTime();
                if (waitingTime > WAIT_FOR_USERS_CONNECT_TIMEOUT) {
                    logger.warn("[ALARM] User[" + e.getKey() + "] Did not connected to messaging server during [" + waitingTime + "]ms");
                    newFlashUsers.remove(e.getKey());
                }
            }
        }

        private void checkFlashUsersDetached() {
            FastMap<Long, AbstractPaperGameUser> usrs = usersManager.getAllPaperGameUsers();
            logger.debug("check all connected users {users size: " + usrs.size() + "} if any it detached");
            for (FastMap.Entry<Long, AbstractPaperGameUser> e = usrs.head(), end = usrs.tail(); (e = e.getNext()) != end;) {
                AbstractPaperGameUser usr = e.getValue();
                if (usr.isDetached()) {
                    logger.warn("[ALARM] User[" + e.getKey() + "] is detached but is still in memory. Removing him");
                    usersManager.onUserDisconnection(usr);
                    // here is double check:
                    usrs.remove(e.getKey());
                    continue;
                }
                if (usr.getId() != e.getKey()) {
                    logger.warn("[ALARM] User is in memory BUT does not have UserDbId same as his key in memory. key[" + e.getKey() + "], userDbId[" + usr.getId() + "]");
                    usrs.remove(e.getKey());
                }
            }
        }

        private void checkFlashUsersPing() {
            FastMap<Long, AbstractPaperGameUser> usrs = usersManager.getAllPaperGameUsers();
            logger.debug("checkFlashUsersPing, users size: " + usrs.size());
            for (FastMap.Entry<Long, AbstractPaperGameUser> e = usrs.head(), end = usrs.tail(); (e = e.getNext()) != end;) {
                AbstractPaperGameUser usr = e.getValue();

                if (usr.checkPingTimeout(PING_TIMEOUT)) {
                    logger.warn("[ALARM] User[" + e.getKey() + "] is online and in memory BUT there are no PING from him for[" + usr.getPingTimeout() + "]ms. Removing him");
                    usersManager.onUserDisconnection(usr);
                    // here is double check:
                    usrs.remove(e.getKey());
                    usr.disconnect(null);
                }
            }
        }

        public void shutDown() {
            shutdown = true;
            this.interrupt();
        }
    }

}
